// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zenbase/concepts.h>
#include <zencore/fmtutils.h>

ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
#include <cxxopts.hpp>
#include <sol/sol.hpp>
ZEN_THIRD_PARTY_INCLUDES_END

#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>

namespace zen::LuaConfig {

std::string MakeSafePath(const std::string_view Path);
void		EscapeBackslash(std::string& InOutString);

class OptionValue
{
public:
	virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0;
	virtual void Parse(sol::object Object)											   = 0;

	virtual ~OptionValue() {}
};

class StringOption : public OptionValue
{
public:
	explicit StringOption(std::string& Value);
	virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override;
	virtual void Parse(sol::object Object) override;
	std::string& Value;
};

class FilePathOption : public OptionValue
{
public:
	explicit FilePathOption(std::filesystem::path& Value);
	virtual void		   Print(std::string_view, zen::StringBuilderBase& StringBuilder) override;
	virtual void		   Parse(sol::object Object) override;
	std::filesystem::path& Value;
};

class BoolOption : public OptionValue
{
public:
	explicit BoolOption(bool& Value);
	virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder);
	virtual void Parse(sol::object Object);
	bool&		 Value;
};

template<Integral T>
class NumberOption : public OptionValue
{
public:
	explicit NumberOption(T& Value) : Value(Value) {}
	virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(fmt::format("{}", Value)); }
	virtual void Parse(sol::object Object) override { Value = Object.as<T>(); }
	T&			 Value;
};

class LuaContainerWriter
{
public:
	LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent);
	~LuaContainerWriter();
	void BeginContainer(std::string_view Name);
	void WriteValue(std::string_view Name, std::string_view Value);
	void EndContainer();

private:
	zen::StringBuilderBase& StringBuilder;
	const std::size_t		InitialIndent;
	std::string				LocalIndent;
};

class StringArrayOption : public OptionValue
{
public:
	explicit StringArrayOption(std::vector<std::string>& Value);
	virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override;
	virtual void Parse(sol::object Object) override;

private:
	std::vector<std::string>& Value;
};

std::shared_ptr<OptionValue> MakeOption(std::string& Value);
std::shared_ptr<OptionValue> MakeOption(std::filesystem::path& Value);

template<Integral T>
std::shared_ptr<OptionValue>
MakeOption(T& Value)
{
	return std::make_shared<NumberOption<T>>(Value);
};

std::shared_ptr<OptionValue> MakeOption(bool& Value);
std::shared_ptr<OptionValue> MakeOption(std::vector<std::string>& Value);

struct Option
{
	std::string					 CommandLineOptionName;
	std::shared_ptr<OptionValue> Value;
};

struct Options
{
public:
	template<typename T>
	void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "")
	{
		OptionMap.insert_or_assign(std::string(Key),
								   Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)});
	};

	void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult);
	void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult);
	void Touch(std::string_view Key);
	void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult);

private:
	void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult);

	std::unordered_map<std::string, Option> OptionMap;
	std::unordered_set<std::string>			UsedKeys;
};

}  // namespace zen::LuaConfig
